using System;
using System.Drawing;

namespace PacSnake
{
    /// <summary>
    /// Enum for the possible moving directions of the snake
    /// </summary>
    public enum MoveDirection
    {
        Up = 1,
        Down = 2,
        Left = 3,
        Right = 4
    }
    
    
    /// <summary>
    /// The Snake class represents the snake, the properties of the snake and
    /// methods for moving and deciding moving directions.
    /// </summary>
    public class Snake
    {
        // Sets the body size of the snake
        private const int BODY_SIZE = 20;

        // Direction our snake is moving
        private Rectangle direction = new Rectangle(0,0,0,0);
        
        // Length of the snake
        private int length = 0;
        
        // Array of positions of each body part
        private Rectangle[] body;
        
        // The direction the snake moves if no key is pressed
        private MoveDirection desiredDirection = MoveDirection.Left;
        
        // Check if the snake is a dead snake
        private bool isDead = false;


        #region Constructor
        
        /// <summary>
        /// Constructor: Initializes the snake's images.
        /// </summary>
        public Snake()
        {
            InitSnakeImages();
        }
        
        #endregion
        
        #region Setting snake position, length and growing

        /// <summary>
        /// Set start position of the snake.
        /// </summary>
        /// <param name="xPos">X-coordinate of the snake's head</param>
        /// <param name="yPos">Y-coordinate of the snake's head</param>
        /// <param name="xOffset">Level X-offset</param>
        /// <param name="yOffset">Level Y-offset</param>
        public void SetStartPosition(int xPos, int yPos, int xOffset,
            int yOffset)
        {
            isDead = false;
            
            // Default moving direction is to the left
            direction.X = -1;
            direction.Y = 0;

            // Set the start position of the snake
            int xstartpos = (xPos - 1) * BODY_SIZE + xOffset;
            int ystartpos = (yPos - 1) * BODY_SIZE + yOffset;
            
            // Set the position and size of each body part of the snake
            for (int i = 0; i < length; i++)
            {
                body[i].X = i * BODY_SIZE + xstartpos;
                body[i].Y = ystartpos;
                body[i].Width  = BODY_SIZE;
                body[i].Height = BODY_SIZE;
            }
        }


        /// <summary>
        /// Sets by how many body parts the snake grows when eating food.
        /// </summary>
        /// <param name="growWith">Number of body parts to grow</param>
        public void Grow(int growWith) 
        {
            length += growWith;
        }


        /// <summary>
        /// Sets the length and maximum length of the snake
        /// </summary>
        /// <param name="nrOfCandy">Maximum length</param>
        public void SetSnakeLength(int nrOfCandy)
        {
            // Initial length of the snake is always 5
            length = 5;
            
            // The maximum length of the snake is its initial size + the number
            // of pices of food in the level
            body = new Rectangle[nrOfCandy + length];
        }

        #endregion
        
        #region Check if snake dies

        /// <summary>
        /// Check if the head of the snake crashed into a body part
        /// </summary>
        /// <returns>True if snake bumps into itself, otherwise false</returns>
        public bool CheckCollision()
        {
            for (int i = 1; i < length; i++)
                if (body[i].IntersectsWith(body[0]))
                {
                    isDead = true;
                    return true;
                }

            return false;
        }
        
        #endregion

        #region Moving the snake and deciding directions
        
        /// <summary>
        /// Updates the new position of the snake.
        /// </summary>
        public void MoveBody()
        {
            // Make sure the end element is correct incase we grow
            body[length] = body[length - 1];

            // Body length
            for (int i = length; i > 0; i--)
                body[i] = body[i - 1];

            // Update our new snakes head pos
            body[0].X = body[0].X + direction.X * BODY_SIZE;
            body[0].Y = body[0].Y + direction.Y * BODY_SIZE;
        }
        

        /// <summary>
        /// Checks the direction(s) the snake can move to depending on its
        /// current position.
        /// </summary>
        /// <param name="level">Level object that contains level layout</param>
        public void checkDirections(Level level)
        {
            if (direction.X == 0 && direction.Y == -1) // Snake goes up
            {
                bool wallOnLeft = level.IsWallThere(-BODY_SIZE, 0, body[0]);
                bool wallOnRight = level.IsWallThere(BODY_SIZE, 0, body[0]);
                bool wallInFront = level.IsWallThere(0, -BODY_SIZE, body[0]);
                DecideNewHorizontalDirection(
                    wallOnLeft, wallOnRight, wallInFront);
            } 
            else if (direction.X == 0 && direction.Y == 1) // Snake goes down
            {
                bool wallOnLeft = level.IsWallThere(-BODY_SIZE, 0, body[0]);
                bool wallOnRight = level.IsWallThere(BODY_SIZE, 0, body[0]);
                bool wallInFront = level.IsWallThere(0, BODY_SIZE, body[0]);
                DecideNewHorizontalDirection(
                    wallOnLeft, wallOnRight, wallInFront);
            } 
            else if (direction.X == -1 && direction.Y == 0) // Snake moves left
            {
                bool wallAbove = level.IsWallThere(0, -BODY_SIZE, body[0]);
                bool wallBelow = level.IsWallThere(0, BODY_SIZE, body[0]);
                bool wallInFront = level.IsWallThere(-BODY_SIZE, 0, body[0]);
                DecideNewVerticalDirection(wallAbove, wallBelow, wallInFront);
            } 
            else if (direction.X == 1 && direction.Y == 0) // Snake goes right
            {
                bool wallAbove = level.IsWallThere(0, -BODY_SIZE, body[0]);
                bool wallBelow = level.IsWallThere(0, BODY_SIZE, body[0]);
                bool wallInFront = level.IsWallThere(BODY_SIZE, 0, body[0]);
                DecideNewVerticalDirection(wallAbove, wallBelow, wallInFront);
            }
        }


        /// <summary>
        /// Checks the new horizontal direction when the snake is currently
        /// moving vertically. When there's a wall in front of the snake, and
        /// there's a wall on the left or right, the snake can only go in one
        /// direction. Otherwise, it will follow the desired direction that is
        /// set when the user presses an arrow key.
        /// </summary>
        /// <param name="wallOnLeft">Wall on left of snake's head</param>
        /// <param name="wallOnRight">Wall on right of snake's head</param>
        /// <param name="wallInFront">Wall in front of snake's head</param>
        private void DecideNewHorizontalDirection(bool wallOnLeft,
            bool wallOnRight, bool wallInFront) 
        {
            if (!wallOnLeft && desiredDirection == MoveDirection.Left)
            {
                // Make the snake go left
                direction.X = -1;
                direction.Y = 0;
                desiredDirection = 0;
            } 
            else if (!wallOnRight && desiredDirection == MoveDirection.Right)
            {
                // Make the snake go right
                direction.X = 1;
                direction.Y = 0;
                desiredDirection = 0;
            } 
            else if (wallInFront)
            {
                // Oops, a wall, will we go left or right?
                if (wallOnLeft)
                {
                    // On the left there's a wall, so let's go right
                    direction.X = 1;
                    direction.Y = 0;
                } 
                else if (wallOnRight)
                {
                    // On the right there's a wall, so let's go left
                    direction.X = -1;
                    direction.Y = 0;
                } 
                else
                {
                    // No wall left or right, so let's follow desired direction
                    // Left or right
                    if (desiredDirection == MoveDirection.Left)
                    {
                        direction.X = -1;
                        direction.Y = 0;
                    } 
                    else
                    {
                        direction.X = 1;
                        direction.Y = 0;
                    }
                }
                // Reset desired direction
                desiredDirection = 0;
            }
        }


        /// <summary>
        /// Checks the new vertical direction when the snake is currently
        /// moving horizontically. When there's a wall in front of the snake,
        /// and there's a wall on the left or right, the snake can only go in
        /// one direction. Otherwise, it will follow the desired direction that
        /// is set when the user presses an arrow key.
        /// </summary>
        /// <param name="wallAbove">Wall above snake's head</param>
        /// <param name="wallBelow">Wall below snake's head</param>
        /// <param name="wallInFront">Wall in front of snake's head</param>
        private void DecideNewVerticalDirection(bool wallAbove, bool wallBelow,
            bool wallInFront) 
        {
            if (!wallAbove && desiredDirection == MoveDirection.Up)
            {
                // Make the snake go up
                direction.X = 0;
                direction.Y = -1;
                desiredDirection = 0;
            } 
            else if (!wallBelow && desiredDirection == MoveDirection.Down)
            {
                // Make the snake go down
                direction.X = 0;
                direction.Y = 1;
                desiredDirection = 0;
            } 
            else if (wallInFront)
            {
                // Oops, a wall, will we go down or up?
                if (wallBelow)
                {
                    // Below there a wall, so let's go up
                    direction.X = 0;
                    direction.Y = -1;
                } 
                else if (wallAbove)
                {
                    // Above there a wall, so let's go down
                    direction.X = 0;
                    direction.Y = 1;
                } 
                else
                {
                    // No wall above or below, so follow desired direction
                    // Up or down
                    if (desiredDirection == MoveDirection.Up)
                    {
                        direction.X = 0;
                        direction.Y = -1;
                    } 
                    else
                    {
                        direction.X = 0;
                        direction.Y = 1;
                    }
                }
                // Reset desired direction
                desiredDirection = 0;
            }
        }

        #endregion

        #region Getting the snake images

        /// <summary>
        /// Get the image of the snake's head depending on the direction it's
        /// moving in
        /// </summary>
        /// <returns>Head image</returns>
        public Image GetHeadImage()
        {
            if (direction.X == 0 && direction.Y == -1) // Snake moves up
                if (isDead)
                    return snakeDeadU;
                else
                    return snakeHeadU;
            else if (direction.X == 0 && direction.Y == 1) // Snake moves down
                if (isDead)
                    return snakeDeadD;
                else
                    return snakeHeadD;
            else if (direction.X == -1 && direction.Y == 0) // Snake moves left
                if (isDead)
                    return snakeDeadL;
                else
                    return snakeHeadL;
            else // Snake moves right
                if (isDead)
                return snakeDeadR;
            else
                return snakeHeadR;
        }


        /// <summary>
        /// Get the image of the snake's body depending on the direction it's
        /// moving in
        /// </summary>
        /// <param name="i"></param>
        /// <returns>Body image</returns>
        public Image GetBodyImage(int i)
        {
            // Body of the snake makes a corner
            if (body[i-1].X != body[i+1].X && body[i-1].Y != body[i+1].Y)
            {
                if (body[i].Y < body[i-1].Y && body[i].X > body[i+1].X ||
                    body[i].X > body[i-1].X && body[i].Y < body[i+1].Y)
                    return snakeCornerUR;
                else if (body[i].X > body[i-1].X && body[i].Y > body[i+1].Y ||
                    body[i].Y > body[i-1].Y && body[i].X > body[i+1].X)
                    return snakeCornerLR;
                else if (body[i].Y > body[i-1].Y && body[i].X < body[i+1].X ||
                    body[i].X < body[i-1].X && body[i].Y > body[i+1].Y)
                    return snakeCornerLL;
                else
                    return snakeCornerUL;
            } 
            else
            {
                // Straight body part, vertical or horizontal
                if (body[i-1].X == body[i+1].X) 
                    return snakeStraightV;
                else
                    return snakeStraightH;
            }
        }


        /// <summary>
        /// Get the image of the snake's tail depending on the direction it's
        /// moving in
        /// </summary>
        /// <returns>Tail image</returns>
        public Image GetTailImage() 
        {
            if (body[length-1].X == body[length-2].X)
            {
                // Snake moves up or down
                if (body[length-1].Y < body[length-2].Y)
                    return snakeTailD;
                else
                    return snakeTailU;
            } 
            else 
            {
                // Snake moves left or right
                if (body[length-1].X < body[length-2].X)
                    return snakeTailR;
                else
                    return snakeTailL;
            }
        }

        #endregion

        #region Snake Images
        
        // The images for all the snake's body parts
        private Image snakeCornerLL, snakeCornerLR, snakeCornerUL,
                      snakeCornerUR;
        private Image snakeHeadR, snakeHeadL, snakeHeadU, snakeHeadD;
        private Image snakeTailR, snakeTailL, snakeTailU, snakeTailD;
        private Image snakeStraightH, snakeStraightV;
        private Image snakeDeadR, snakeDeadL, snakeDeadU, snakeDeadD;
        
        
        /// <summary>
        /// We only use one images for the head, tail, corner and straight body
        /// part. For all the directions we can rotate the images. The images
        /// we use are the head and tail image moving right, the horizontal
        /// straight body part and the lower left corner: |_
        /// </summary>
        private void InitSnakeImages()
        {
            snakeCornerLL = Image.FromFile("skins/ice/snakecornerll.gif");
            snakeCornerLR = Image.FromFile("skins/ice/snakecornerll.gif");
            snakeCornerUL = Image.FromFile("skins/ice/snakecornerll.gif");
            snakeCornerUR = Image.FromFile("skins/ice/snakecornerll.gif");
        
            snakeHeadR = Image.FromFile("skins/ice/snakeheadr.gif");
            snakeHeadL = Image.FromFile("skins/ice/snakeheadr.gif");
            snakeHeadU = Image.FromFile("skins/ice/snakeheadr.gif");
            snakeHeadD = Image.FromFile("skins/ice/snakeheadr.gif");
        
            snakeTailR = Image.FromFile("skins/ice/snaketailr.gif");
            snakeTailL = Image.FromFile("skins/ice/snaketailr.gif");
            snakeTailU = Image.FromFile("skins/ice/snaketailr.gif");
            snakeTailD = Image.FromFile("skins/ice/snaketailr.gif");
        
            snakeStraightH = Image.FromFile("skins/ice/snakestraighth.gif");
            snakeStraightV = Image.FromFile("skins/ice/snakestraighth.gif");
            
            snakeDeadR = Image.FromFile("skins/ice/snakedeadr.gif");
            snakeDeadL = Image.FromFile("skins/ice/snakedeadr.gif");
            snakeDeadU = Image.FromFile("skins/ice/snakedeadr.gif");
            snakeDeadD = Image.FromFile("skins/ice/snakedeadr.gif");
            
            snakeCornerLR.RotateFlip(RotateFlipType.Rotate270FlipNone);
            snakeCornerUL.RotateFlip(RotateFlipType.Rotate90FlipNone);
            snakeCornerUR.RotateFlip(RotateFlipType.Rotate180FlipNone);
        
            snakeHeadL.RotateFlip(RotateFlipType.Rotate180FlipNone);
            snakeHeadU.RotateFlip(RotateFlipType.Rotate270FlipNone);
            snakeHeadD.RotateFlip(RotateFlipType.Rotate90FlipNone);
            
            snakeTailL.RotateFlip(RotateFlipType.Rotate180FlipNone);
            snakeTailU.RotateFlip(RotateFlipType.Rotate270FlipNone);
            snakeTailD.RotateFlip(RotateFlipType.Rotate90FlipNone);
            
            snakeStraightV.RotateFlip(RotateFlipType.Rotate90FlipNone);
            
            snakeDeadL.RotateFlip(RotateFlipType.Rotate180FlipNone);
            snakeDeadU.RotateFlip(RotateFlipType.Rotate270FlipNone);
            snakeDeadD.RotateFlip(RotateFlipType.Rotate90FlipNone);
        }
        
        #endregion
        
        #region Properties

        public int Length
        {
            get { return length; }
        }

        public MoveDirection DesiredDirection
        {
            set { desiredDirection = value; }
        }
        
        public Rectangle[] Body
        {
            get { return body; }
        }
        
        public bool IsDead
        {
            set { isDead = value; }
        }
        
        #endregion
    }
}
